﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Reflection;
using NUnit.Framework;
using ShrinkWrap.HttpHandler;
using ShrinkWrap.Minifier;
using Moq;
using System.Web;
using ShrinkWrap.Test.Minifier;
using ShrinkWrap.Configuration;
using ShrinkWrap.Exceptions;

namespace ShrinkWrap.Test.HttpHandler
{
    [TestFixture]
    public class MinifyingHandlerFixture
    {
        IHttpRequest request;
        IHttpResponse response;
        MinifyingHandler httpHandler;

        [SetUp]
        public void SetUp()
        {
            request = getExampleRequest();
            response = getResponse(new StringBuilder());
            httpHandler = getHandler(() => "");
        }

        [TestCase()]
        public void TestOutput()
        {
            var expected = "Some CSS or javascript string";
            var actual = new StringBuilder();
            var httpHandler = getHandler(() => expected);
            var response = getResponse(actual);

            httpHandler.ProcessAggregation(request, response, null, null);

            Assert.AreEqual(expected, actual.ToString());
            Assert.AreEqual(0, response.Expires);
        }

        [TestCase(0)]
        [TestCase(1)]
        [TestCase(60)]
        [TestCase(-1)]
        public void TestExpiry(int expiry)
        {
            var config = new Mock<ShrinkWrapConfiguration>();
            config.Setup(x => x.Expiry).Returns(expiry);
            httpHandler.config = config.Object;

            httpHandler.ProcessAggregation(request, response, null, null);

            Assert.AreEqual(expiry, response.Expires);
        }

        [TestCase(null)]
        [TestCase("deflate")]
        public void TestNotGzippedWhenNotRequested(String acceptEncoding)
        {
            request.Headers["Accept-Encoding"] = acceptEncoding;

            httpHandler.ProcessAggregation(request, response, null, null);

            Assert.IsNull(response.GetHeader("Content-Encoding"));
            Assert.IsNotInstanceOf<GZipStream>(response.Filter);
        }

        [TestCase("gzip")]
        [TestCase("gzip,deflate")]
        [TestCase("deflate,gzip")]
        public void TestGzippedWhenRequested(String acceptEncoding)
        {
            request.Headers["Accept-Encoding"] = acceptEncoding;

            httpHandler.ProcessAggregation(request, response, null, null);

            Assert.AreEqual("gzip", response.GetHeader("Content-Encoding"));
            Assert.IsInstanceOf<GZipStream>(response.Filter);
        }

        [TestCase(200)]
        public void TestSuccessStatusCode(int statusCode)
        {
            var request = new HttpRequestWrapper(getExampleUri(), new NameValueCollection(), "GET");

            httpHandler.ProcessAggregation(request, response, null, null); 
            Assert.AreEqual(statusCode, response.StatusCode);
        }

        [TestCase(404)]
        public void TestKnownErrorStatusCode(int statusCode)
        {
            var httpHandler = getHandler(delegate { throw new ShrinkWrapException(null, null, null); });
            var request = new HttpRequestWrapper(getExampleUri(), new NameValueCollection(), "GET");

            httpHandler.ProcessAggregation(request, response, null, null);

            Assert.AreEqual(statusCode, response.StatusCode);
        }

        [TestCase(500)]
        public void TestUnknownErrorStatusCode(int statusCode)
        {
            var httpHandler = getHandler(delegate { throw new Exception(); });
            var request = new HttpRequestWrapper(getExampleUri(), null, "GET");

            httpHandler.ProcessAggregation(request, response, null, null);

            Assert.AreEqual(statusCode, response.StatusCode);
        }

        [TestCase(null)]
        [TestCase("gzip")]
        public void TestVaryHeaderPresent(String acceptEncoding)
        {
            request.Headers["Accept-Encoding"] = "gzip";

            httpHandler.ProcessAggregation(request, response, null, null);

            Assert.AreEqual("Accept-Encoding", response.GetHeader("Vary"));
        }

        [TestCase("DELETE")]
        [TestCase("PUT")]
        [TestCase("POST")]
        [TestCase(null)]
        public void TestInvalidVerbsRejected(String verb)
        {
            request.Verb = verb;

            httpHandler.ProcessAggregation(request, response, null, null);

            Assert.AreEqual(405, response.StatusCode);
        }

        [TestCase]
        public void TestHeadResponse()
        {
            var actual = new StringBuilder();
            var response = getResponse(actual);
            request.Verb = "HEAD";

            httpHandler.ProcessAggregation(request, response, null, null);

            Assert.AreEqual(200, response.StatusCode);
            Assert.IsEmpty(actual.ToString());
        }
        

        MinifyingHandler getHandler(Func<String> aggregatorResult)
        {
            var aggregator = getAggregator(aggregatorResult);

            var httpHandler = new Mock<MinifyingHandler>();
            httpHandler.CallBase = true;
            httpHandler.Setup(x => x.GetAggregator(null, null, getExampleUri())).Returns(aggregator);
            httpHandler.Object.config = new ShrinkWrapConfiguration();

            return httpHandler.Object;
        }

        Aggregator getAggregator(Func<String> aggregatorResult)
        {
            var uri = getExampleUri();
            var aggregator = new Mock<Aggregator>(new object[] { null, uri, null, 1 });
            aggregator.Setup(x => x.GetAggregate()).Returns(aggregatorResult);

            return aggregator.Object;
        }

        IHttpResponse getResponse(StringBuilder written)
        {
            var response = new Mock<IHttpResponse>();
            response.SetupAllProperties();
            response.Object.Filter = new MemoryStream();

            var headers = new NameValueCollection();
            response.Setup(x => x.GetHeader(It.IsAny<String>())).Returns((String key) => headers[key]);
            response.Setup(x => x.AddHeader(It.IsAny<String>(), It.IsAny<String>())).Callback(delegate(String key, String value) { headers[key] = value; });

            response.Setup(x => x.Write(It.IsAny<String>())).Callback(delegate(String data) { written.Append(data); });

            // Default code is success
            response.Object.StatusCode = 200;

            return response.Object;
        }

        [TestCase(typeof(CssHandler))]
        [TestCase(typeof(JavascriptHandler))]
        public void TestGetAggregator(Type type)
        {
            var aggregator = handler(type).GetAggregator(null, null, getExampleUri());
            Assert.IsInstanceOf<CachingAggregator>(aggregator);
        }

        MinifyingHandler handler(Type handlerType)
        {
            return handlerType.GetConstructor(Type.EmptyTypes).Invoke(null) as MinifyingHandler;
        }

        CompositeUri getExampleUri()
        {
            return new CompositeUri("http://example.com");
        }

        IHttpRequest getExampleRequest()
        {
            return new HttpRequestWrapper(getExampleUri(), new NameValueCollection(), "GET");
        }
    }
}
